#!/usr/bin/evn python
# -*- coding: utf-8 -*-
"""
-------------------------------------------------
   文件名称 :     __init__.py
   文件功能描述 :   功能描述
   创建人 :       小钟同学
   创建时间 :          2021/12/6
-------------------------------------------------
   修改描述-2021/12/6:         
-------------------------------------------------
"""
from pydantic import BaseModel, ValidationError, create_model
from typing import Any, Dict, Optional, Sequence, Type
from pydantic.error_wrappers import ErrorList
from typing import Any, List, Optional
from pydantic import EnumError, StrRegexError

# 动态的创建一个错误的模型
RequestErrorModel: Type[BaseModel] = create_model("Request")


class RequestValidationError(ValidationError):
    def __init__(self, errors: Sequence[ErrorList], *, body: Any = None) -> None:
        self.body = body
        super().__init__(errors, RequestErrorModel)


def parse_error(
        err: Any, field_names: List, raw: bool = True
) -> Optional[dict]:
    """
    Parse single error object (such as pydantic-based or fastapi-based) to dict

    :param err: Error object
    :param field_names: List of names of the field that are already processed
    :param raw: Whether this is a raw error or wrapped pydantic error
    :return: dict with name of the field (or "__all__") and actual message
    """

    if isinstance(err.exc, EnumError):
        permitted_values = ", ".join(
            [f"'{val}'" for val in err.exc.enum_values]
        )
        message = f"Value is not a valid enumeration member; " \
                  f"permitted: {permitted_values}."
    elif isinstance(err.exc, StrRegexError):
        message = "Provided value doesn't match valid format."
    else:
        message = str(err.exc) or ""

    if hasattr(err.exc, "code") and err.exc.code.startswith("error_code"):
        error_code = int(err.exc.code.split(".")[-1])
    else:
        # default error code for non-custom errors is 400
        error_code = 400

    if not raw:
        if len(err.loc_tuple()) == 2:
            if str(err.loc_tuple()[0]) in ["body", "query"]:
                name = err.loc_tuple()[1]
            else:
                name = err.loc_tuple()[0]
        elif len(err.loc_tuple()) == 1:
            if str(err.loc_tuple()[0]) == "body":
                name = "__all__"
            else:
                name = str(err.loc_tuple()[0])
        else:
            name = "__all__"
    else:
        if len(err.loc_tuple()) == 2:
            name = str(err.loc_tuple()[0])
        elif len(err.loc_tuple()) == 1:
            name = str(err.loc_tuple()[0])
        else:
            name = "__all__"

    if name in field_names:
        return None

    if message and not any(
            [message.endswith("."), message.endswith("?"), message.endswith("!")]
    ):
        message = message + "."
    message = message.capitalize()

    return {"name": name, "message": message, "error_code": error_code}


def raw_errors_info(exc):
    status_code = getattr(exc, "status_code", 400)
    fields = raw_errors_to_fields(exc.raw_errors)
    if fields:
        error_codes = set(list(map(lambda x: x["error_code"], fields)))
    else:
        error_codes = [getattr(exc, "error_code", status_code)]
    # 信息
    message = getattr(exc, "message", "Validation error")
    # 解析错误信息
    if message and not any([message.endswith("."), message.endswith("?"), message.endswith("!")]):
        message = message + "."  # pragma: no cover
    data = {"message": message, "fields": fields}
    return data


def raw_errors_to_fields(raw_errors: List) -> List[dict]:
    """
    Translates list of raw errors (instances) into list of dicts with name/msg

    :param raw_errors: List with instances of raw error
    :return: List of dicts (1 dict for every raw error)
    """
    fields = []
    for top_err in raw_errors:
        if hasattr(top_err.exc, "raw_errors"):
            for err in top_err.exc.raw_errors:
                # This is a special case when errors happen both in request
                # handling & internal validation
                if isinstance(err, list):
                    err = err[0]
                field_err = parse_error(
                    err,
                    field_names=list(map(lambda x: x["name"], fields)),
                    raw=True,
                )
                if field_err is not None:
                    fields.append(field_err)
        else:
            field_err = parse_error(
                top_err,
                field_names=list(map(lambda x: x["name"], fields)),
                raw=False,
            )
            if field_err is not None:
                fields.append(field_err)
    return fields
